﻿/*
    FramelessHelper, an easy way to support move/resize on
    frameless toplevel windows.
    Copyright (C) 2011  Nishant Parashar
    Email:- nishcode (at) gmail (dot) com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QRubberBand>
#include <QMouseEvent>
#include "FramelessHelper.h"


/**
 * @brief 计算、保存鼠标的位置等信息的类
 */
class CursorPosCalculator
{

public:
    CursorPosCalculator();
    void reset();
    void recalculate( const QPoint& global_mouse_pos, const QRect& frame_rect );

public:
    bool m_on_edges;
    bool m_on_left_edge;
    bool m_on_right_edge;
    bool m_on_top_edge;
    bool m_on_bottom_edge;
    bool m_on_top_left_edge;
    bool m_on_bottom_left_edge;
    bool m_on_top_right_edge;
    bool m_on_bottom_right_edge;

    static int m_border_width;                                                                                                     // 边框的宽
};
//TODO: Should not be static.
int CursorPosCalculator::m_border_width = RESIZE_BORDER_WIDTH;




class WidgetData
{
public:
    WidgetData( FramelessHelperImpl* impl, QWidget* top_level_widget );
    ~WidgetData();

    //void setWidget( QWidget* topLevelWidget );
    QWidget* widget();
    void handleWidgetEvent( QEvent* event );
    void updateRubberBandStatus();

private:
    void updateCursorShape( const QPoint& global_mouse_pos );
    void resizeWidget( const QPoint& global_mouse_pos );
    void moveWidget( const QPoint& global_mouse_pos );

    void handleMousePressEvent( QMouseEvent* event );
    void handleMouseReleaseEvent( QMouseEvent* event );
    void handleMouseMoveEvent( QMouseEvent* event );
    void handleLeaveEvent( QEvent* event );
    void handleHoverMoveEvent( QHoverEvent* event );

private:
    FramelessHelperImpl* m_impl;
    QRubberBand* m_rubber_band;
    bool m_left_btn_pressed;
    QWidget* m_widget;
    QPoint m_drag_pos;
    CursorPosCalculator m_pressed_mouse_pos;
    CursorPosCalculator m_move_mouse_pos;
    bool m_cursor_shape_changed;
    Qt::WindowFlags m_window_flags;
};




class FramelessHelperImpl
{
public:
    QHash< QWidget*, WidgetData* > m_hash_widget_data;
    bool m_widget_movable;
    bool m_widget_resizable;
    bool m_use_ruberband_on_resize;
    bool m_use_ruberband_on_move;
};








CursorPosCalculator::CursorPosCalculator()
{
    reset();
}


void CursorPosCalculator::reset()
{
    m_on_edges = false;
    m_on_left_edge = false;
    m_on_right_edge = false;
    m_on_top_edge = false;
    m_on_bottom_edge = false;
    m_on_top_left_edge = false;
    m_on_bottom_left_edge = false;
    m_on_top_right_edge = false;
    m_on_bottom_right_edge = false;
}


void CursorPosCalculator::recalculate( const QPoint& global_mouse_pos, const QRect& frame_rect )
{
    // 获取鼠标x,y坐标
    int global_mouse_x = global_mouse_pos.x();
    int global_mouse_y = global_mouse_pos.y();

    // 获取framex,y坐标
    int frame_x = frame_rect.x();
    int frame_y = frame_rect.y();

    // 获取frame的宽、高
    int frame_width = frame_rect.width();
    int frame_height = frame_rect.height();

    // 判断是否在窗口边界
    m_on_left_edge = global_mouse_x >= frame_x &&
            global_mouse_x <= frame_x + m_border_width;


    m_on_right_edge = global_mouse_x >= frame_x + frame_width - m_border_width &&
            global_mouse_x <= frame_x + frame_width;

    m_on_top_edge = global_mouse_y >= frame_y &&
            global_mouse_y <= frame_y + m_border_width;


    m_on_bottom_edge = global_mouse_y >= frame_y + frame_height - m_border_width &&
            global_mouse_y <= frame_y + frame_height;

    m_on_top_left_edge = m_on_top_edge && m_on_left_edge;
    m_on_bottom_left_edge = m_on_bottom_edge && m_on_left_edge;
    m_on_top_right_edge = m_on_top_edge && m_on_right_edge;
    m_on_bottom_right_edge = m_on_bottom_edge && m_on_right_edge;

    //only these checks would be enough
    m_on_edges = m_on_left_edge || m_on_right_edge ||
            m_on_top_edge || m_on_bottom_edge;

}





WidgetData::WidgetData( FramelessHelperImpl* impl, QWidget* top_level_widget )
{
    m_impl = impl;
    m_widget = top_level_widget;
    m_left_btn_pressed = false;
    m_rubber_band = 0;
    m_cursor_shape_changed = false;

    m_window_flags = m_widget->windowFlags();

    //---from Qt docs of setWindowFlags()----
    //Note: This function calls setParent() when
    //changing the flags for a window, causing the
    //widget to be hidden. You must call show()
    //to make the widget visible again..

    bool visible = m_widget->isVisible();

    m_widget->setMouseTracking( true );
    m_widget->setWindowFlags( Qt::WindowMinMaxButtonsHint| Qt::FramelessWindowHint );

    //Bug fix, mouse move events does not propagate from child widgets.
    //so need the hover events.
    m_widget->setAttribute( Qt::WA_Hover );

    updateRubberBandStatus();

    m_widget->setVisible( visible );
}


WidgetData::~WidgetData()
{
    //---from Qt docs of setWindowFlags()----
    //Note: This function calls setParent() when
    //changing the flags for a window, causing the
    //widget to be hidden. You must call show()
    //to make the widget visible again..

    bool visible = m_widget->isVisible();

    m_widget->setMouseTracking( false );
    m_widget->setWindowFlags( m_window_flags );//^  Qt::WindowMinMaxButtonsHint ^ Qt::FramelessWindowHint );
    m_widget->setAttribute( Qt::WA_Hover, false );

    m_widget->setVisible( visible );

    delete m_rubber_band;
}


void WidgetData::updateRubberBandStatus()
{
    if ( m_impl->m_use_ruberband_on_move || m_impl->m_use_ruberband_on_resize )
    {
        if ( !m_rubber_band )
            m_rubber_band = new QRubberBand( QRubberBand::Rectangle );
    }
    else
    {
        delete m_rubber_band;
        m_rubber_band = 0;
    }
}


QWidget* WidgetData::widget()
{
    return m_widget;
}


void WidgetData::handleWidgetEvent( QEvent* event )
{
    switch ( event->type() )
    {
    default: //qDebug() << "Event = " << event;
        break;
    case QEvent::MouseButtonPress:
        handleMousePressEvent( static_cast<QMouseEvent*>( event ) );
        break;

    case QEvent::MouseButtonRelease:
        handleMouseReleaseEvent( static_cast<QMouseEvent*>( event ) );
        break;

    case QEvent::MouseMove:
        handleMouseMoveEvent( static_cast<QMouseEvent*>( event ) );
        break;

    case QEvent::Leave:
        handleLeaveEvent( event );
        break;

        //Bug fix, hover event is necessary coz child widget does not
        //propagate mousemove events. so the cursor remains in edge shape
        //even in middle of widget.
    case QEvent::HoverMove:
        handleHoverMoveEvent( static_cast<QHoverEvent*>( event ) );
        break;
        //case QEvent::Enter:
        //qDebug() << "Enter event";//d->handleEnterEvent( event );
        //break;
    }
}

/**
 * @brief 检测鼠标如果在窗口边界时显示相应的鼠标样式
 */
void WidgetData::updateCursorShape( const QPoint& global_mouse_pos )
{
    if ( m_widget->isFullScreen() || m_widget->isMaximized() )
    {
        if ( m_cursor_shape_changed )
            m_widget->unsetCursor();

        return;
    }

    m_move_mouse_pos.recalculate( global_mouse_pos, m_widget->frameGeometry() );

    if( m_move_mouse_pos.m_on_top_left_edge || m_move_mouse_pos.m_on_bottom_right_edge )
    {
        m_widget->setCursor( Qt::SizeFDiagCursor );
        m_cursor_shape_changed = true;
    }
    else if( m_move_mouse_pos.m_on_top_right_edge || m_move_mouse_pos.m_on_bottom_left_edge )
    {
        m_widget->setCursor( Qt::SizeBDiagCursor );
        m_cursor_shape_changed = true;
    }
    else if( m_move_mouse_pos.m_on_left_edge || m_move_mouse_pos.m_on_right_edge )
    {
        m_widget->setCursor( Qt::SizeHorCursor );
        m_cursor_shape_changed = true;
    }
    else if( m_move_mouse_pos.m_on_top_edge || m_move_mouse_pos.m_on_bottom_edge )
    {
        m_widget->setCursor( Qt::SizeVerCursor );
        m_cursor_shape_changed = true;
    }
    else
    {
        if ( m_cursor_shape_changed )
        {
            m_widget->unsetCursor();
            m_cursor_shape_changed = false;
        }
    }
}


void WidgetData::resizeWidget( const QPoint& global_mouse_pos )
{
    QRect orig_rect;

    if ( m_impl->m_use_ruberband_on_resize )
        orig_rect = m_rubber_band->frameGeometry();
    else
        orig_rect = m_widget->frameGeometry();


    int left = orig_rect.left();
    int top = orig_rect.top();
    int right = orig_rect.right();
    int bottom = orig_rect.bottom();
    orig_rect.getCoords( &left, &top, &right, &bottom );

    int minWidth = m_widget->minimumWidth();
    int minHeight = m_widget->minimumHeight();

    if ( m_pressed_mouse_pos.m_on_top_left_edge )
    {
        left = global_mouse_pos.x();
        top = global_mouse_pos.y();
    }
    else if ( m_pressed_mouse_pos.m_on_bottom_left_edge )
    {
        left = global_mouse_pos.x();
        bottom = global_mouse_pos.y();
    }
    else if ( m_pressed_mouse_pos.m_on_top_right_edge )
    {
        right = global_mouse_pos.x();
        top = global_mouse_pos.y();
    }
    else if ( m_pressed_mouse_pos.m_on_bottom_right_edge )
    {
        right = global_mouse_pos.x();
        bottom = global_mouse_pos.y();
    }
    else if ( m_pressed_mouse_pos.m_on_left_edge )
    {
        left = global_mouse_pos.x();
    }
    else if ( m_pressed_mouse_pos.m_on_right_edge )
    {
        right = global_mouse_pos.x();
    }
    else if ( m_pressed_mouse_pos.m_on_top_edge )
    {
        top = global_mouse_pos.y();
    }
    else if ( m_pressed_mouse_pos.m_on_bottom_edge )
    {
        bottom = global_mouse_pos.y();
    }

    QRect new_rect( QPoint(left, top), QPoint(right, bottom) );

    if ( new_rect.isValid() )
    {
        if ( minWidth > new_rect.width() )
        {
            //determine what has caused the width change.
            if( left != orig_rect.left() )
                new_rect.setLeft( orig_rect.left() );
            else
                new_rect.setRight( orig_rect.right() );
        }
        if ( minHeight > new_rect.height() )
        {
            //determine what has caused the height change.
            if ( top != orig_rect.top() )
                new_rect.setTop( orig_rect.top() );
            else
                new_rect.setBottom( orig_rect.bottom() );
        }

        if ( m_impl->m_use_ruberband_on_resize )
        {
            m_rubber_band->setGeometry( new_rect );
        }
        else
        {
            m_widget->setGeometry( new_rect );
        }
    }
    else
    {
        //qDebug() << "Calculated Rect is not valid" << newRect;
    }

}


void WidgetData::moveWidget( const QPoint& global_mouse_pos )
{
    if ( m_impl->m_use_ruberband_on_move )
    {
        m_rubber_band->move( global_mouse_pos - m_drag_pos );
    }
    else
    {
        m_widget->move( global_mouse_pos - m_drag_pos );
    }
}


void WidgetData::handleMousePressEvent( QMouseEvent* event )
{
    if ( event->button() == Qt::LeftButton )
    {
        m_left_btn_pressed = true;

        QRect frame_rect = m_widget->frameGeometry();
        m_pressed_mouse_pos.recalculate( event->globalPos(), frame_rect );

        m_drag_pos = event->globalPos() - frame_rect.topLeft();

        if ( m_pressed_mouse_pos.m_on_edges  )
        {
            if ( m_impl->m_use_ruberband_on_resize )
            {
                m_rubber_band->setGeometry( frame_rect );
                m_rubber_band->show();
            }
        }
        else if ( m_impl->m_use_ruberband_on_move )
        {
            m_rubber_band->setGeometry( frame_rect );
            m_rubber_band->show();
        }

    }
}


void WidgetData::handleMouseReleaseEvent( QMouseEvent* event )
{
    if ( event->button() == Qt::LeftButton )
    {
        m_left_btn_pressed = false;
        m_pressed_mouse_pos.reset();
        if ( m_rubber_band && m_rubber_band->isVisible() )
        {
            m_rubber_band->hide();
            m_widget->setGeometry( m_rubber_band->geometry() );
        }
    }
}


void WidgetData::handleMouseMoveEvent( QMouseEvent* event )
{
    if ( m_left_btn_pressed )
    {
        if ( m_impl->m_widget_resizable && m_pressed_mouse_pos.m_on_edges )
        {
            resizeWidget( event->globalPos() );
        }
        else if ( m_impl->m_widget_movable )
        {
            moveWidget( event->globalPos() );
        }
    }
    else if ( m_impl->m_widget_resizable )
    {
        updateCursorShape( event->globalPos() );
    }

}


void WidgetData::handleLeaveEvent( QEvent* /*event*/ )
{
    if ( !m_left_btn_pressed )
        m_widget->unsetCursor();
}


void WidgetData::handleHoverMoveEvent( QHoverEvent* event )
{
    if ( m_impl->m_widget_resizable )
        updateCursorShape( m_widget->mapToGlobal( event->pos() ) );
}






/**
 * @brief 初始化m_impl
 * @param parent
 */
FramelessHelper::FramelessHelper( QObject* parent )
    : QObject( parent ),
      m_impl( new FramelessHelperImpl )
{
    m_impl->m_widget_movable = true;
    m_impl->m_widget_resizable = true;
    m_impl->m_use_ruberband_on_resize = false;
    m_impl->m_use_ruberband_on_move = false;
}

/**
 * @brief 清理内存
 */
FramelessHelper::~FramelessHelper()
{
    QList<QWidget*> keys = m_impl->m_hash_widget_data.keys();
    int size = keys.size();
    for ( int i = 0; i < size; ++i )
    {
        delete m_impl->m_hash_widget_data.take( keys[i] );
    }

    delete m_impl;
}


bool FramelessHelper::eventFilter( QObject *obj, QEvent *event )
{
    QEvent::Type type = event->type();

    if ( type == QEvent::MouseMove ||
         type == QEvent::HoverMove ||
         type == QEvent::MouseButtonPress ||
         type == QEvent::MouseButtonRelease ||
         type == QEvent::Leave
         )
    {
        WidgetData* data = m_impl->m_hash_widget_data.value( static_cast<QWidget*>(obj) );
        if ( data )
        {
            data->handleWidgetEvent( event );
        }

    }

    return false;
}


void FramelessHelper::activateOn( QWidget* top_level_widget )
{
    if ( m_impl->m_hash_widget_data.contains( top_level_widget ) )
        return;

    WidgetData* data = new WidgetData( m_impl, top_level_widget );
    m_impl->m_hash_widget_data.insert( top_level_widget, data );

    top_level_widget->installEventFilter( this );
}


void FramelessHelper::removeFrom( QWidget* top_level_widget )
{
    WidgetData* data = m_impl->m_hash_widget_data.take( top_level_widget );
    if ( data )
    {
        top_level_widget->removeEventFilter( this );
        delete data;
    }
}


void FramelessHelper::setWidgetMovable( bool movable )
{
    m_impl->m_widget_movable = movable;
}


bool FramelessHelper::isWidgetMovable()
{
    return m_impl->m_widget_movable;
}


void FramelessHelper::setWidgetResizable( bool resizable )
{
    m_impl->m_widget_resizable = resizable;
}


bool FramelessHelper::isWidgetResizable()
{
    return m_impl->m_widget_resizable;
}


void FramelessHelper::useRubberBandOnMove( bool use )
{
    m_impl->m_use_ruberband_on_move = use;
    QList<WidgetData*> list = m_impl->m_hash_widget_data.values();
    int size = list.size();
    for ( int i = 0; i < size; ++i )
        list[i]->updateRubberBandStatus();
}


bool FramelessHelper::isUsingRubberBandOnMove()
{
    return m_impl->m_use_ruberband_on_move;
}


/**
 * @brief 设置是否启用橡皮筋
 * @param use
 */
void FramelessHelper::useRubberBandOnResize( bool use )
{
    m_impl->m_use_ruberband_on_resize = use;
    QList<WidgetData*> list = m_impl->m_hash_widget_data.values();
    int size = list.size();
    for ( int i = 0; i < size; ++i )
        list[i]->updateRubberBandStatus();
}

bool FramelessHelper::isUsingRubberBandOnResisze()
{
    return m_impl->m_use_ruberband_on_resize;
}

void FramelessHelper::setBorderWidth( int new_border_width )
{
    //TODO: Make it non-static.
    if ( new_border_width >= 0 )
        CursorPosCalculator::m_border_width = new_border_width;
}

int FramelessHelper::borderWidth()
{
    return CursorPosCalculator::m_border_width;
}
